%{
 #include <stdio.h>
 #include <stdlib.h>
 #include "Analyzer.h"
 #include "SingleCommand.h"
 #include "ParseException.h"
 #include "printouts.h"
 #include <list>

 using namespace std;
 
extern "C"
{    
        int yylex(void);  
		YY_BUFFER_STATE yy_scan_string(char *);
		void yy_delete_buffer(YY_BUFFER_STATE);
		int yywrap();
		char* yytext;
}

void addCommand(char* );
void addArg(char* arg);
void addRed(char* , int);
void addBg();
void yyerror(const char *);
int yyparse(void);
void replaceAll(std::string&, const std::string&, const std::string&);

list<SingleCommand *>* cmdList = NULL;

%} 
%union
{
  char* str;
}

%token<str> STRING PIPE RED_INPUT RED_OUTPUT RED_OUTPUT_APPEND RED_OUTPUT_BOTH RED_OUTPUT_BOTH_APPEND RED_ERR RED_ERR_APPEND AMP 
%%
command_line:	/* empty */
				| piped_commands bg 
				;
piped_commands:	command 
		    	| piped_commands PIPE { free($2); } command
				;
command:		exec args io
				;
exec:			STRING { addCommand($1); }
				;
args:			/* empty */ 
				| args STRING { addArg($2); }
				;
io:				/* empty */
				| io RED_INPUT STRING {  addRed($3, RED_INPUT); free($2); }
				| io RED_OUTPUT STRING {  addRed($3, RED_OUTPUT); free($2); }
				| io RED_OUTPUT_APPEND STRING {  addRed($3, RED_OUTPUT_APPEND); free($2); }
				| io RED_OUTPUT_BOTH STRING {  addRed($3, RED_OUTPUT_BOTH); free($2); }
				| io RED_OUTPUT_BOTH_APPEND STRING {  addRed($3, RED_OUTPUT_BOTH_APPEND); free($2); }
				| io RED_ERR STRING  {  addRed($3, RED_ERR); free($2); }
				| io RED_ERR_APPEND STRING  {  addRed($3, RED_ERR_APPEND); free($2); }
				;
bg:				/* empty */
				| AMP { addBg(); free($1);}
				;
%%

/**
 * Spusti lexikalni a syntaktickou analyzu vstupniho retezce.
 * V pripade chyby vyhodi vyjimku (ParseException). Volajci musi 
 * po dokonceni uvolnit pamet alokovanou pro seznam a jeho prvky.
 * 
 * @param   line     radka s prikazy
 * @return           seznam prikazu
 */
list<SingleCommand *>* analyze(char* line)
{
  YY_BUFFER_STATE buf = yy_scan_string(line);
  yyparse();
  yy_delete_buffer(buf);
  list<SingleCommand *>* output = cmdList;
  cmdList = NULL;
  return output;
}

/**
 * Vlozi do seznamu prikaz.
 * 
 * @param cmd       prikaz
 */
void addCommand(char* cmd)
{
    if(cmdList == NULL)
    {
        cmdList = new list<SingleCommand *> ();
    }
    SingleCommand* command = new SingleCommand();
    string cmdstr = string(cmd);
    replaceAll(cmdstr, "\\ ", " ");
    command->insertArg(cmdstr); 
    command->setInBackground(false);
    command->setFileOutAppend(false);
    command->setFileErrAppend(false);
    cmdList->push_back(command);
    free(cmd);
    cmd = NULL;
}

/**
 * Vlozi do seznamu argument.
 * 
 * @param arg  argument
 */
void addArg(char* arg)
{
    SingleCommand* command = cmdList->back();
    string argstr = string(arg);
    replaceAll(argstr, "\\ ", " ");
    command->insertArg(argstr);
    free(arg);
    arg = NULL;
}

/**
 * Vlozi do seznamu presmerovani do souboru.
 * 
 * @param fileName  soubor
 * @param type      typ presmerovani
 */
void addRed(char* fileName, int type)
{
    string fileNameStr = string(fileName);
    replaceAll(fileNameStr, "\\ ", " ");
    free(fileName);
    SingleCommand* command = cmdList->back();
    switch(type)
    {
        case RED_INPUT:
            command->setFileIn(fileNameStr);
            break;
        case RED_OUTPUT:
            command->setFileOut(fileNameStr);
            command->setFileOutAppend(false);
            break;
        case RED_OUTPUT_APPEND:
            command->setFileOut(fileNameStr);
            command->setFileOutAppend(true);
            break;
        case RED_ERR:
            command->setFileErr(fileNameStr);
            command->setFileErrAppend(false);
            break;
        case RED_ERR_APPEND:
            command->setFileErr(fileNameStr);
            command->setFileErrAppend(true);
            break;
        case RED_OUTPUT_BOTH:
            command->setFileOut(fileNameStr);
            command->setFileOutAppend(false);
            command->setFileErr(fileNameStr);
            command->setFileErrAppend(false);
            break;
        case RED_OUTPUT_BOTH_APPEND:
            command->setFileOut(fileNameStr);
            command->setFileOutAppend(true);
            command->setFileErr(fileNameStr);
            command->setFileErrAppend(true);
            break;
        default :
            break;
    }
}

/**
 * Vlozi zpracovani na pozadi do seznamu.
 */
void addBg()
{
    for (list<SingleCommand *>::iterator it = cmdList->begin(); it != cmdList->end(); it++)
    {
		(*it)->setInBackground(true);
	}
}

/**
 * Zpracovani chyby pri parsovani.
 */
void yyerror(const char *s)
{   
    if(cmdList != NULL)
    {
        for (list<SingleCommand *>::iterator it = cmdList->begin(); it != cmdList->end(); it++)
        {
		    if ((*it) != NULL)
		    {
			    delete (*it);
			    (*it) = NULL;
		    }
	    }
	    delete(cmdList);
	    cmdList = NULL;
    } 
    
    string error = "";
    error += YACC_SYNTAX_ERROR; 
    error += yytext;
    throw ParseException(error); 
}

/**
 * Ukonci skenovani na konci souboru.
 */
int yywrap(void)
{
    return 1;
}

/**
 * Nahradi vsechny vyskyty podretezce jinym retezcem.
 * 
 * @param   str   retezec
 * @param   from  nahrazovany podretezec
 * @param   to    novy podretezec
 */
void replaceAll(std::string& str, const std::string& from, const std::string& to) 
{
    if(from.empty())
        return;
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos) 
    {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length();
    }
}
